package com.yan.exception;

import com.yan.common.util.AjaxEntity;
import com.yan.common.util.HttpStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.nio.file.AccessDeniedException;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 全局异常处理
 * @author yanchen
 * @since 2021/1/28 16:06
 */
@RestControllerAdvice
@Slf4j
public class ExceptionAdvice {
    @Value("${server.port}")
    private String serverPort;


    private String handlerRequestUrl(HttpServletRequest request){
        String requestUrl = request.getRequestURI();
        return "http://localhost:" + serverPort + requestUrl;
    }

    /**
     * 请求参数的类型与处理器方法参数类型不匹配：比如形参是int，传入string
     * @param e 参数校验异常
     * @param request request
     * @return AjaxEntity
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public AjaxEntity<Object>  handlerException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
        log.error("1、请求地址：{}，参数验证失败：{}", handlerRequestUrl(request),e.getMessage(),e);
        return AjaxEntity.fail("参数类型传递错误，"+
                "参数类型："+ Objects.requireNonNull(e.getRequiredType()).getName()+" "+
                "传入类型："+ Objects.requireNonNull(e.getRootCause()).getMessage());
    }

    /**
     * 对接口方法形参的校验没有通过：@Length、@Email、、、、
     * @param e 参数校验异常
     * @param request request
     * @return AjaxEntity
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public AjaxEntity<Object>  handlerException(ConstraintViolationException e, HttpServletRequest request) {
        String message = e.getConstraintViolations().stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.joining(", "));
        log.error("message:{}",message);
        log.error("2、请求地址：{}，参数验证失败：{}", handlerRequestUrl(request), e.getMessage(), e);
        return AjaxEntity.fail("参数校验失败："+ message);
    }

    /**
     * 对实体类的属性进行校验时没有通过：@Length、@Email、、、、
     * MethodArgumentNotValidException 异常类继承自该异常类，功能一致，处理异常的逻辑完全参考
     * @param e 参数校验异常
     * @param request request
     * @return AjaxEntity
     */
    @ExceptionHandler(BindException.class)
    public AjaxEntity<Object> handlerException(BindException e, HttpServletRequest request) {
        log.error("3、请求地址：{}，参数验证失败：{}", handlerRequestUrl(request), e.getMessage(), e);
        return AjaxEntity.fail("参数校验失败："+ e.getAllErrors().get(0).getDefaultMessage());
    }
    /**
     * 参数验证失败异常
     * 对实体类的属性进行校验时没有通过：@Length、@Email、、、、
     * @param e 参数验证失败异常
     * @param request request
     * @return AjaxEntity
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public AjaxEntity<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e,
                                                                    HttpServletRequest request) {
        String message = Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage();
        log.error("4、请求地址：{}，参数验证失败：{},{}", handlerRequestUrl(request), e.getObjectName(),message, e);
        return AjaxEntity.fail("参数校验失败："+ message);
    }

    /**
     * 请求参数缺失，没有传入必要的参数
     * @param e 参数校验异常
     * @param request request
     * @return AjaxEntity
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public AjaxEntity<Object> handlerException(MissingServletRequestParameterException e,
                                               HttpServletRequest request) {
        log.error("5、请求地址：{},参数校验失败：{}", handlerRequestUrl(request), e.getMessage(), e);
        return AjaxEntity.fail("参数校验失败："+ e.getMessage());
    }

    /**
     * 权限校验异常
     * @param e 权限校验异常
     * @param request request
     * @return AjaxEntity
     */
    @ExceptionHandler(AccessDeniedException.class)
    public AjaxEntity<Object> handleAccessDeniedException(AccessDeniedException e,
                                                          HttpServletRequest request) {
        log.error("请求地址：{},权限校验失败：{}", handlerRequestUrl(request), e.getMessage());
        return AjaxEntity.fail(HttpStatus.FORBIDDEN.code(), "没有权限，请联系管理员授权");
    }

    /**
     * 请求方式不支持
     * @param e 请求方式不支持
     * @param request request
     * @return AjaxEntity
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public AjaxEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
                                                                  HttpServletRequest request) {
        log.error("请求地址：{}，不支持{}请求", handlerRequestUrl(request), e.getMethod());
        return AjaxEntity.fail(e.getMessage());
    }


    /**
     * 拦截错误SQL异常
     * @param e 异常
     * @param request request
     * @return AjaxEntity
     */
    // @ExceptionHandler(BadSqlGrammarException.class)
    public AjaxEntity<Object> handleBadSqlGrammarException(BadSqlGrammarException e,
                                                           HttpServletRequest request) {
        log.error("数据库异常1、请求地址：{}，发生数据库异常：SQL错误,{}", handlerRequestUrl(request), e.getSql(),e);
        return AjaxEntity.fail(HttpStatus.ERROR.code(), "数据库异常：SQL错误");
    }

    /**
     * 可以拦截表示违反数据库的完整性约束导致的异常。
     * @param e 异常
     * @param request request
     * @return AjaxEntity
     */
    // @ExceptionHandler(DataIntegrityViolationException.class)
    public AjaxEntity<Object> handleDataIntegrityViolationException(DataIntegrityViolationException e,
                                                                    HttpServletRequest request) {
        String message = e.getCause().getMessage();
        log.error("数据库异常2、请求地址：{}，发生数据库异常:{}", handlerRequestUrl(request), message,e);
        return AjaxEntity.fail(HttpStatus.ERROR.code(),  "数据库异常："+message);
    }

    /**
     * 可以拦截违反数据库的非完整性约束导致的异常，可能也会拦截一些也包括 SQL 语句错误、连接问题、权限问题等各种数据库异常。
     * @param e 异常
     * @param request request
     * @return AjaxEntity
     */
    // @ExceptionHandler(UncategorizedSQLException.class)
    public AjaxEntity<Object> handleUncategorizedSqlException(UncategorizedSQLException e,
                                                              HttpServletRequest request) {
        log.error("数据库异常3、请求地址：{}，发生数据库异常:{}", handlerRequestUrl(request), e.getSql(),e);
        return AjaxEntity.fail(HttpStatus.ERROR.code(),  "数据库异常！");
    }


    /**
     * 拦截非法参数异常
     * @param e 异常(IllegalArgumentException)
     * @param request request
     * @return AjaxEntity
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public AjaxEntity<Object> illegalArgumentException(IllegalArgumentException e,
                                                       HttpServletRequest request) {
        String message = e.getMessage();
        log.error("请求地址：{}，发生非法参数异常：{}", handlerRequestUrl(request), message, e);
        return AjaxEntity.fail(message != null ? message : "非法参数");
    }

    /**
     * 拦截404异常
     * @param e 404异常
     * @param request request
     * @return AjaxEntity
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public AjaxEntity<Object> handle404(NoHandlerFoundException e, HttpServletRequest request) {
        log.warn("请求地址：{}，接口未找到: {}", handlerRequestUrl(request), e.getMessage());
        return AjaxEntity.fail(404, "接口不存在");
    }


    /**
     * 业务自定义异常
     * @param e 业务自定义异常
     * @param request request
     * @return AjaxEntity
     */
    @ExceptionHandler(ServiceException.class)
    public AjaxEntity<Object> handleServiceException(ServiceException e,
                                                     HttpServletRequest request) {
        Integer code = e.getCode();
        log.error("请求地址：{}，发生业务自定义异常：{}",handlerRequestUrl(request),e.getMessage(), e);
        return code != null ? AjaxEntity.fail(code, e.getMessage()) : AjaxEntity.fail(e.getMessage());
    }

    /**
     * 拦截未知的运行时异常
     * @param e 异常(RuntimeException)
     * @param request request
     * @return AjaxEntity
     */
    @ExceptionHandler(RuntimeException.class)
    public AjaxEntity<Object> handleRuntimeException(RuntimeException e,
                                                     HttpServletRequest request) {
        log.error("请求地址：{}，发生未知的运行时异常：{}", handlerRequestUrl(request),e.getMessage(), e);
        String message = e.getMessage();
        return AjaxEntity.fail(message != null ? message : "系统运行时异常");
    }

    /**
     * 全局异常
     * @param e 全局异常(Exception)
     * @param request request
     * @return AjaxEntity
     */
    @ExceptionHandler(Exception.class)
    public AjaxEntity<Object> handleException(Exception e,
                                              HttpServletRequest request){
        log.error("请求地址：{}，发生系统异常",handlerRequestUrl(request), e);
        return AjaxEntity.fail(e.getMessage());
    }

}